#include <reg52.h>

/*******************************************************************************
														LCD Declarations / Variables
*******************************************************************************/
#define PDATA    P0
sbit RS    = P2^0;
sbit RW    = P2^1;
sbit EN    = P2^2;
sbit D7    = P0^7;

/*******************************************************************************
														Other Constants / Variables
*******************************************************************************/
const char MERCURY[]	= "Mercury";
const char VENUS[]		= "Venus";
const char MARS[]			= "Mars";
const char JUPITER[]	= "Jupiter";
const char SATURN[]		= "Saturn";
const char URANUS[]		= "Uranus";
const char NEPTUNE[]	= "Neptune";
const char PLUTO[]		= "Pluto";

const char MESSAGE_WEIGHT[] = "Your weight on";
const char MESSAGE_AGE[] = "Your age on";
const char UNIT_WEIGHT[] = "kg";
const char UNIT_AGE[] = "yr";

const char PROMPT_WEIGHT_1[] = "Set your weight:";
const char PROMPT_WEIGHT_2[] = "XX kg";

const char PROMPT_AGE_1[] = "Set your age:";
const char PROMPT_AGE_2[] = "XX years";


// Defining buttons for selecting a planet
sbit BUTTON_PLANET_MERCURY	= P1^0;
sbit BUTTON_PLANET_VENUS		= P1^1;
sbit BUTTON_PLANET_MARS			= P1^2;
sbit BUTTON_PLANET_JUPITER	= P1^3;
sbit BUTTON_PLANET_SATURN		= P1^4;
sbit BUTTON_PLANET_URANUS		= P1^5;
sbit BUTTON_PLANET_NEPTUNE	= P1^6;
sbit BUTTON_PLANET_PLUTO		= P1^7;

// Defining buttons for setting values and mode
sbit BUTTON_INC = P3^7;
sbit BUTTON_DEC = P3^6;
sbit BUTTON_MODE = P3^5;

int value = 43;		// value of age or weight depending on the mode

// Defining various modes of the application
#define MODE_WEIGHT_CONVERSION 1
#define MODE_AGE_CONVERSION 2
int mode = MODE_WEIGHT_CONVERSION;


/*******************************************************************************
															LCD Functions
*******************************************************************************/

// Wait for the LCD controller to respond/acknowledge.
// This method should be called before sending a command to
// the LCD controller.
void Wait_LCD()
{
	EN = 0; RS = 0; RW = 1; D7 = 1; EN = 1;
	while(D7)
	{
		EN = 0; EN = 1;
	}
	EN = 0;	RW = 0;
}

void Init_LCD()
{
	RS = 0; PDATA = 0x34; EN = 1; EN = 0;
	Wait_LCD();
	RS = 0;	PDATA = 0x3C;	EN = 1;	EN = 0;
	Wait_LCD();
	RS = 0;	PDATA= 0x0C; EN = 1; EN = 0;
	Wait_LCD();
	RS = 0; PDATA= 0x06; EN=1; EN=0;
	Wait_LCD();
}

// Sends command to the LCD controller to clear the screen.
void Clear_LCD()
{
	RS = 0; PDATA = 0x01; EN = 1; EN = 0;
	Wait_LCD();
}

// Write a character on the LCD screen.
// Value of X is between 0 and 15 while y is 1 for the first line and 2 for the second.
void Write_Char(unsigned char c,int x, int y)
{
	int location;
	if(y==1)
	{
		location = 0x80 + x;
	}
	else
	{
		location = 0x80 + 0x40 + x;
	}
	RS = 0;	PDATA=location;	EN = 1;	EN=0;
	Wait_LCD();
	RS = 1;	PDATA = c; EN = 1; EN = 0;
	Wait_LCD();
}

// Write a line on the LCD screen.
// The line number value can be either 1 or 2 for the 2 line display.
void Write_Line(char* line, int lineNumber)
{
	int i=0;
	while(*line != '\0') {
		Write_Char(*line, i, lineNumber);
		i++;		line++;
	}
}

/*******************************************************************************
															Other Functions
*******************************************************************************/
void DisplayPrompt()
{
	Clear_LCD();
	switch (mode)
	{
		case MODE_WEIGHT_CONVERSION:
			Write_Line(&PROMPT_WEIGHT_1, 1);
			Write_Line(&PROMPT_WEIGHT_2, 2);
			break;
		case MODE_AGE_CONVERSION:
			Write_Line(&PROMPT_AGE_1, 1);
			Write_Line(&PROMPT_AGE_2, 2);
			break;
	}
	Write_Char('0' + (value/10), 0, 2);
	Write_Char('0' + (value - ((value/10)*10)), 1, 2);
}

// TODO: unit can be changed to a pointer thus allowing "years" instead of "yr"
void DisplayValueOnPlanet(float calculatedValue, char* planet, char line1[], char unit[])
{
	int i=0;
	char line2[16];
	int d1, d2, d3, d4, d5;	// Digits that represent the number in the following format: 1234.5
	
	d1 = (int)(calculatedValue)/1000;
	d2 = (int)(calculatedValue - (d1*1000))/100;
	d3 = (int)(calculatedValue - ((d1*1000) + (d2*100)))/10;
	d4 = (int)(calculatedValue - ((d1*1000) + (d2*100) + (d3*10)));
	d5 = (int)((calculatedValue*10) - (((int)calculatedValue)*10));
	
	while(*planet != '\0') {
		line2[i] = *planet;
		i++;
		planet++;
	}
	line2[i++] = ':';
	line2[i++] = ' ';
	
	if (d1 > 0)
	{
		line2[i++] = ('0' + d1);
	}
	if (d1 > 0 || d2 > 0)
	{
		line2[i++] = ('0' + d2);
	}
	if (d1 > 0 || d2 > 0 || d3 > 0)
	{
		line2[i++] = ('0' + d3);
	}
	line2[i++] = ('0' + d4);
	line2[i++] = '.';
	line2[i++] = ('0' + d5);
	line2[i++] = ' ';
	line2[i++] = unit[0];
	line2[i++] = unit[1];
	line2[i++] = '\0';

	Clear_LCD();
	Write_Line(line1, 1);
	Write_Line(&line2, 2);
}

/*******************************************************************************
															Other Functions
*******************************************************************************/
// Initialize the ports and set the appropriate ports as inputs
void Init_Button_Ports()
{
	P1 = 0xFF;
	P3 = 0xFF;
}

// The following method switches the application between
// available modes.
void Change_Mode()
{
	switch (mode)
	{
		case MODE_WEIGHT_CONVERSION:
			mode = MODE_AGE_CONVERSION;
			break;
		case MODE_AGE_CONVERSION:
			mode = MODE_WEIGHT_CONVERSION;
			break;
		default:
			mode = MODE_WEIGHT_CONVERSION;
	}
}

// Note: the "seconds" argument may not necessarily translate
// to accurate seconds in time.
void delay(int seconds)
{
	int i,j;
	for(i=0; i<seconds;i++)
	{
		for(j=0;j<10000;j++);
	}
}

void PlanetButtonPressRoutine(bit button, char planetName[],
		float weightMultiplier, float ageMultiplier)
{
	float calculatedValue;
	if (~button)
	{
		switch(mode)
		{
			case MODE_WEIGHT_CONVERSION:
				calculatedValue = value * weightMultiplier;
				DisplayValueOnPlanet(calculatedValue, planetName, MESSAGE_WEIGHT, UNIT_WEIGHT);
				delay(12);
				DisplayPrompt();
				break;
			case MODE_AGE_CONVERSION:
				calculatedValue = value * ageMultiplier;
				DisplayValueOnPlanet(calculatedValue, planetName, MESSAGE_AGE, UNIT_AGE);
				delay(12);
				DisplayPrompt();
				break;
		}
	}
}

void CheckForPlanetButtonPresses()
{
	PlanetButtonPressRoutine(BUTTON_PLANET_MERCURY, MERCURY, 0.378f, 15.0f);
	PlanetButtonPressRoutine(BUTTON_PLANET_VENUS, VENUS, 0.907f, 15.0f);
	PlanetButtonPressRoutine(BUTTON_PLANET_MARS, MARS, 0.377, 15.0f);
	PlanetButtonPressRoutine(BUTTON_PLANET_JUPITER, JUPITER, 2.364f, 15.0f);
	PlanetButtonPressRoutine(BUTTON_PLANET_SATURN, SATURN, 1.064f, 15.0f);
	PlanetButtonPressRoutine(BUTTON_PLANET_URANUS, URANUS, 0.889f, 15.0f);
	PlanetButtonPressRoutine(BUTTON_PLANET_NEPTUNE, NEPTUNE, 1.125f, 15.0f);
}

// Main program logic
void main (void)
{
	char changeModeMsg[] = "Mode Changed";
	Init_Button_Ports();
	Init_LCD();
	DisplayPrompt();
	
	while(1)
	{
		if (~BUTTON_MODE)
		{
			Change_Mode();
			Clear_LCD();
			Write_Line(&changeModeMsg, 1);
			while(~BUTTON_MODE);	// wait until the button is released
			delay(5);
			DisplayPrompt();
		}
		else	if (~BUTTON_INC)
		{
			value++;
			if (value > 99)
			{
				value = 99;
			}
			DisplayPrompt();
			delay(4);
		}
		else if (~BUTTON_DEC)
		{
			value--;
			if (value < 7)
			{
				value = 7;
			}
			DisplayPrompt();
			delay(4);
		}
		else
		{
			CheckForPlanetButtonPresses();
		}
	}
}